package org.xx.armory.spring5.jdbc;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.util.Pair;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.CallableStatementCreator;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.SqlProvider;
import org.springframework.jdbc.core.SqlReturnResultSet;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.xx.armory.commons.IntegerEnum;
import org.xx.armory.commons.StringEnum;
import org.xx.armory.spring5.DateConverter;

import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.sql.ResultSet.CONCUR_READ_ONLY;
import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
import static java.sql.Types.REF_CURSOR;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static java.util.regex.Pattern.DOTALL;
import static java.util.regex.Pattern.UNICODE_CASE;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.StringUtils.trimToNull;
import static org.apache.commons.text.StringEscapeUtils.escapeJava;
import static org.springframework.core.NestedExceptionUtils.getMostSpecificCause;
import static org.springframework.util.StringUtils.quote;
import static org.xx.armory.commons.Converter.toStr;
import static org.xx.armory.commons.TextUtils.ellipse;
import static org.xx.armory.commons.TextUtils.replaceByChars;
import static org.xx.armory.commons.TextUtils.replaceByRegex;
import static org.xx.armory.commons.Validators.rejectIfBlank;
import static org.xx.armory.commons.Validators.rejectIfNull;
import static org.xx.armory.spring5.jdbc.SqlAndArgsBuilder.RETURN_RESULT_SET_ARG_NAME;
import static org.xx.armory.spring5.jdbc.SqlTypeUtils.sqlTypeName;

public abstract class JdbcDao
        implements InitializingBean {
    /**
     * 用于匹配动态参数的正则表达式。
     * <p>
     * 该正则表达式可以匹配两种参数：{@literal #...#}或者{@literal $...$}。前者表示定位参数，后者表示文本替换参数。</p>
     */
    private static final Pattern PARAMETER_OR_PLACEHOLDER = Pattern.compile(
            "(#([a-zA-Z_][a-zA-Z0-9_]*)#)|(\\$([a-zA-Z_][a-zA-Z0-9_]*)\\$)", CASE_INSENSITIVE | UNICODE_CASE | DOTALL);
    private final Logger logger = LoggerFactory.getLogger(JdbcDao.class);

    protected int resultSetType;

    protected IdentifierCaseMode identifierCaseMode;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DateConverter dateConverter;

    private DateTimeFormatter isoDateFormatter;

    private Pageable pageable;
    private String autoKey;
    private KeyHolder keyHolder;

    private static String getMostSpecificCauseMessage(
            Throwable t
    ) {
        final var cm = getMostSpecificCause(t).getMessage();
        return cm != null ? cm.strip() : "<null>";
    }

    @Override
    public void afterPropertiesSet()
            throws Exception {
        resetPageable();
        resetAutoKey();
        this.keyHolder = null;
        this.resultSetType = REF_CURSOR;
        this.identifierCaseMode = IdentifierCaseMode.UPPER;
        this.isoDateFormatter = this.dateConverter.buildFormatter("yyyy-MM-dd'T'HH:mm:ssZ");
    }

    /**
     * 重置当前的分页参数。
     */
    protected final void resetPageable() {
        this.pageable = null;
    }

    /**
     * 设置当前分页参数。
     *
     * @param pageable
     *         分页参数，{@code null}表示后续查询操作不分页。
     * @return 此对象本身。
     */
    protected JdbcDao setPageable(
            Pageable pageable
    ) {
        this.pageable = pageable;
        return this;
    }

    /**
     * 重置当前自动生成ID的列名，每次update操作之后应当调用此方法。
     */
    protected final void resetAutoKey() {
        this.autoKey = "";
    }

    /**
     * 设置当前自动生成ID的列名。
     *
     * @param autoKey
     *         自动生成ID的列名。
     * @return 此对象本身。
     */
    protected JdbcDao setAutoKey(
            String autoKey
    ) {
        this.autoKey = trimToEmpty(autoKey);
        this.keyHolder = !this.autoKey.isEmpty() ? new GeneratedKeyHolder() : null;
        return this;
    }

    /**
     * 对匹配字符串进行转义。
     * <p>针对通配符 {@literal %_} 进行转义。</p>
     *
     * @param s
     *         需转义的字符串，如果是 {@code null} 或者空字符串则返回空字符串。
     * @return 转义结果。
     * @see #replaceWildcardChars(int, int)
     */
    private String escapeLikeString(
            CharSequence s
    ) {
        if (s == null || s.length() == 0) {
            return "";
        } else {
            return replaceByChars("%_", s, this::replaceWildcardChars);
        }
    }

    /**
     * 将通配符替换为转义后的字符串。
     *
     * @param i
     *         替换序号。
     * @param ch
     *         通配符。
     * @return 转义后的字符串。
     */
    protected final String replaceWildcardChars(
            int i,
            int ch
    ) {
        return new StringBuilder(2).append('\\').appendCodePoint(ch).toString();
    }

    /**
     * 对表示开头匹配的参数值进行转义。
     *
     * <p>如果参数的类型是{@literal string}，并且匹配模式是 {@literal starts-with} ，那么必须将参数值使用该方法进行转义。</p>
     *
     * @param s
     *         需匹配的参数值，如果是空字符串则返回空字符串，否则在末尾加百分号。
     * @return 转义结果。
     * @see #escapeLikeString(CharSequence)
     */
    protected final String escapeStartsWith(
            CharSequence s
    ) {
        final var result = escapeLikeString(s);
        if (result.isEmpty()) {
            return "";
        }

        return result + "%";
    }

    /**
     * 对表示结尾匹配的参数值进行转义。
     *
     * <p>如果参数的类型是{@literal string}，并且匹配模式是 {@literal ends-with} ，那么必须将参数值使用该方法进行转义。</p>
     *
     * @param s
     *         需匹配的参数值，如果是空字符串则返回空字符串，否则在开头加百分号。
     * @return 转义结果。
     * @see #escapeLikeString(CharSequence)
     */
    protected final String escapeEndsWith(
            CharSequence s
    ) {
        final var result = escapeLikeString(s);
        if (result.isEmpty()) {
            return "";
        }

        return "%" + result;
    }

    /**
     * 对表示任意位置匹配的参数值进行转义。
     *
     * <p>如果参数的类型是{@literal string}，并且匹配模式是 {@literal contains} ，那么必须将参数值使用该方法进行转义。</p>
     *
     * @param s
     *         需匹配的参数值，如果是空字符串则返回空字符串，否则在开头和末尾加百分号。
     * @return 转义结果。
     * @see #escapeLikeString(CharSequence)
     */
    protected final String escapeContains(
            CharSequence s
    ) {
        final var result = escapeLikeString(s);
        if (result.isEmpty()) {
            return "";
        }

        return "%" + result + "%";
    }

    /**
     * 将SQL标识符用双引号包装。
     *
     * @param id
     *         SQL标识符。
     * @return 包装的结果。
     * @throws IllegalArgumentException
     *         如果参数 {@code id} 是 {@code null} 或者只包含空白字符。
     */
    private String quoteIdentifier(
            String id
    ) {
        id = rejectIfBlank(id, "id").trim();

        return stream(StringUtils.split(id, '.'))
                .filter(s -> !s.isEmpty())
                .map(s -> {
                    switch (this.identifierCaseMode) {
                        case UPPER:
                            return "\"" + s.toUpperCase() + "\"";
                        case LOWER:
                            return "\"" + s.toLowerCase() + "\"";
                        case KEEP_UP:
                            return "\"" + s + "\"";
                        default:
                            assert false : "Unknown IdentifierCaseMode: " + this.identifierCaseMode;
                            return "";
                    }
                }).collect(Collectors.joining("."));
    }

    /**
     * 将若干SQL标识符用双引号包装。
     *
     * @param idArray
     *         若干SQL标识符。
     * @return 包装的结果。
     */
    protected final String[] quoteIdentifiers(
            String prefix,
            String... idArray
    ) {
        rejectIfNull(idArray, "idArray");
        final var prefix_ = trimToNull(prefix);

        return stream(idArray).filter(id -> !id.isBlank())
                              .map(id -> quoteIdentifier(prefix_ != null ? prefix_ + "." + id : id))
                              .toArray(String[]::new);
    }

    /**
     * 将准备执行的SQL和参数输出到日志。
     *
     * @param sqlAndArgs
     *         待执行的SQL和待执行的参数。
     * @return 已格式化的，可以输出到日志的文本。
     */
    protected final String dumpSqlAndArgs(
            Pair<String, SqlParameter[]> sqlAndArgs
    ) {
        rejectIfNull(sqlAndArgs, "sqlAndArgs");

        final var sb = new StringBuilder();
        sb.append(sqlAndArgs.getFirst());

        final var args = sqlAndArgs.getSecond();
        if (args.length > 0) {
            sb.append("\n**** Parameters ****");
            for (var i = 0; i < sqlAndArgs.getSecond().length; ++i) {
                final var arg = args[i];
                final var index = i + 1;
                final var typeName = sqlTypeName(arg.getSqlType());
                final String dir;
                final String value;
                if (arg.isInputValueProvided()) {
                    dir = "IN";
                    if (arg instanceof SqlParameterValue) {
                        value = dumpObject(((SqlParameterValue) arg).getValue());
                    } else {
                        assert false : "Unknown argument: " + arg;
                        value = "";
                    }
                } else if (arg.isResultsParameter()) {
                    dir = "RET";
                    value = "";
                } else {
                    dir = "OUT";
                    value = "";
                }

                sb.append(String.format("\n[%2d] %-15s %3s : %s", index, typeName, dir, value));
            }
        }

        return sb.toString();
    }

    private String dumpObject(
            Object obj
    ) {
        if (obj == null) {
            return "null";
        } else if (obj instanceof CharSequence) {
            return quote(escapeJava(ellipse((CharSequence) obj, 128)));
        } else if (obj instanceof Number) {
            return obj.toString();
        } else if (obj instanceof Date) {
            return this.isoDateFormatter.format(((Date) obj).toInstant());
        } else {
            return ellipse(obj.toString(), 128);
        }
    }

    /**
     * 获取用于执行SQL命令的{@link JdbcTemplate}对象。
     *
     * @return 用于执行SQL命令的{@link JdbcTemplate}对象。
     */
    protected final JdbcTemplate jdbc() {
        if (this.jdbcTemplate == null) {
            throw new IllegalStateException("this.jdbcTemplate should be initialized");
        }

        return this.jdbcTemplate;
    }

    /**
     * 向SQL命令和参数集合的构造类中添加排序规则。
     *
     * @param sqlAndArgsBuilder
     *         用于创建SQL命令和参数集合的构造类。
     * @param sort
     *         待添加的排序规则。
     */
    public void addSortBy(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            Sort sort
    ) {
        rejectIfNull(sort, "sort");

        if (sort.isUnsorted()) {
            return;
        }

        final var d = new SqlStringBuilder("ORDER BY ", ", ", "");

        for (final var order : sort) {
            if (order == null) {
                continue;
            }
            switch (order.getDirection()) {
                case ASC:
                    d.append(quoteIdentifier(order.getProperty()) + " ASC");
                    break;
                case DESC:
                    d.append(quoteIdentifier(order.getProperty()) + " DESC");
                default:
                    assert false : "Unknown direction: " + order.getDirection();
            }
        }

        sqlAndArgsBuilder.append(d.build());
    }

    /**
     * 将枚举集合转化为数字或者字符串集合。
     *
     * <p>如果参数{@code c}是{@code null}则返回空集合。</p>
     *
     * @param c
     *         待转化的枚举集合。
     * @return 转化结果。
     * @see IntegerEnum
     * @see StringEnum
     */
    protected final Collection<?> escapeEnumSet(
            Collection<? extends Enum<?>> c
    ) {
        if (c == null) {
            return emptySet();
        }

        return c.stream()
                .map(v -> {
                    if (v instanceof IntegerEnum) {
                        return ((IntegerEnum) v).value();
                    } else if (v instanceof StringEnum) {
                        return ((StringEnum) v).stringValue();
                    } else {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(toList());
    }

//    /**
//     * 包装排序规则，用于在SQL命令中对排序规则进行特定的格式化。
//     *
//     * @param order
//     *         排序规则。
//     * @return 可以用于拼接SQL命令的排序SQL。
//     * @throws NullPointerException
//     *         如果参数 {@code order} 是{@code null}。
//     */
//    protected String escapeOrder(
//            Sort.Order order
//    ) {
//        return order.getProperty()
//                + (order.isAscending() ? " ASC" : "DESC")
//                + (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST ? " NULLS FIRST" :
//                order.getNullHandling() == Sort.NullHandling.NULLS_LAST ? " NULLS LAST" :
//                        "");
//    }

    private String escapeCollectionAsPlaceholder(
            Collection<?> c
    ) {
        assert c != null;

        if (c.isEmpty()) {
            return "NULL";
        }

        return c.stream()
                .map(v -> {
                    if (v == null) {
                        return null;
                    } else if (v instanceof Number) {
                        return v.toString();
                    } else if (v instanceof String) {
                        final var s = ((String) v);
                        if (s.isEmpty()) {
                            return "''";
                        } else {
                            // 替换单引号和反斜杠。
                            return "'" + replaceByChars("'\\", s, (index, ch) -> {
                                if (ch == '\'') {
                                    return "''";
                                } else if (ch == '\\') {
                                    return "\\\\";
                                } else {
                                    return String.valueOf((char) ch);
                                }
                            }) + "'";
                        }
                    } else {
                        assert false : "Illegal item of collection: " + v;
                        return null;
                    }
                }).filter(Objects::nonNull)
                .collect(joining(","));
    }

    protected String processPlaceholder(
            SqlParameter arg
    ) {
        assert arg != null;

        if (arg instanceof SqlParameterValue) {
            final var argValue = ((SqlParameterValue) arg).getValue();
            if (argValue == null) {
                return "";
            } else if (argValue instanceof Collection) {
                return escapeCollectionAsPlaceholder((Collection<?>) argValue);
            } else {
                return argValue.toString();
            }
        } else {
            return "";
        }
    }

    private Pair<String, SqlParameter[]> prepareSqlAndArgs(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        assert sqlAndArgsBuilder != null;

        if (this.pageable != null) {
            addSortBy(sqlAndArgsBuilder, this.pageable.getSort());
        }

        final var sql = sqlAndArgsBuilder.buildSql();
        final var args = sqlAndArgsBuilder.getArgs();

        final var args_ = new ArrayList<SqlParameter>(args.size());
        final var sql_ = replaceByRegex(PARAMETER_OR_PLACEHOLDER, sql, (i1, m, s1) -> {
            final var g1 = toStr(m.group(2)).toLowerCase();
            final var g2 = toStr(m.group(4)).toLowerCase();

            if (!g1.isEmpty()) {
                final var arg_ = args.get(g1);
                if (arg_ == null) {
                    throw new InvalidDataAccessResourceUsageException("Cannot find sql argument: \"" + g1 + "\"");
                }
                args_.add(arg_);
                return "?";
            } else if (!g2.isEmpty()) {
                final var arg_ = args.get(g2);
                if (arg_ == null) {
                    throw new InvalidDataAccessResourceUsageException("Cannot find sql argument: \"" + g2 + "\"");
                }
                return processPlaceholder(arg_);
            } else {
                assert false : String.format("Cannot find any valid pattern: %s", m.group());
                return "";
            }
        });

        return Pair.of(sql_, args_.toArray(SqlParameter[]::new));
    }

    private Pair<String, SqlParameter[]> prepareProcedureAndArgs(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            ResultSetExtractor<?> extractor
    ) {
        assert sqlAndArgsBuilder != null;

        final var sql = sqlAndArgsBuilder.buildSql();
        final var args = sqlAndArgsBuilder.getArgs();

        return Pair.of(sql, args.values()
                                .stream()
                                .map(arg -> {
                                    if (arg.isResultsParameter()) {
                                        return new SqlReturnResultSet(RETURN_RESULT_SET_ARG_NAME, extractor);
                                    } else if (RETURN_RESULT_SET_ARG_NAME.equals(arg.getName()) && arg.getSqlType() == REF_CURSOR) {
                                        return new SqlOutParameter(arg.getName(), this.resultSetType, extractor);
                                    } else {
                                        return arg;
                                    }
                                }).toArray(SqlParameter[]::new));
    }

    protected final CallableStatementCreator newCallableStatementCreator(
            Pair<String, SqlParameter[]> sqlAndArgs
    ) {
        return new SimpleCallableStatementCreator(sqlAndArgs);
    }

    protected final <T> List<T> queryList(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            JdbcMapper<T> mapper
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");
        rejectIfNull(mapper, "mapper");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.pageable != null) {
            if (this.logger.isTraceEnabled()) {
                logger.trace("Query list with pageable\n" + dumpSqlAndArgs(sqlAndArgs));
            }
        } else {
            if (this.logger.isTraceEnabled()) {
                logger.trace("Query list\n" + dumpSqlAndArgs(sqlAndArgs));
            }
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? mapper.page(pageable) : mapper.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final <T> T querySingle(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            JdbcMapper<T> mapper
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");
        rejectIfNull(mapper, "mapper");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query single entity\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), mapper.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Integer queryInt(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query int\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.INTEGER_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Integer> queryIntList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Integer>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.INTEGER_MAPPER.page(this.pageable) : ScalarMappers.INTEGER_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Long queryLong(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query long\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.LONG_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Long> queryLongList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Long>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(),
                                            this.pageable != null ? ScalarMappers.LONG_MAPPER.page(this.pageable) : ScalarMappers.LONG_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Double queryDouble(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query double\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.DOUBLE_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Double> queryDoubleList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Double>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.DOUBLE_MAPPER.page(this.pageable) : ScalarMappers.DOUBLE_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final BigDecimal queryBigDecimal(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query BigDecimal\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.BIG_DECIMAL_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<BigDecimal> queryBigDecimalList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<BigDecimal>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.BIG_DECIMAL_MAPPER.page(this.pageable) : ScalarMappers.BIG_DECIMAL_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Date queryDate(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query date\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.DATE_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Date> queryDateList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Date>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.DATE_MAPPER.page(this.pageable) : ScalarMappers.DATE_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final <E extends Enum<E>> E queryEnum(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            Class<E> clazz
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query enum " + clazz.getTypeName() + "\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.mapToEnum(1, clazz).single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final <E extends Enum<E>> List<E> queryEnumList(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            Class<E> clazz
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<" + clazz.getTypeName() + ">\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.mapToEnum(1, clazz).page(this.pageable) : ScalarMappers.mapToEnum(1, clazz).all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Boolean queryBoolean(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query boolean\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.BOOLEAN_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Boolean> queryBooleanList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Boolean>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.BOOLEAN_MAPPER.page(this.pageable) : ScalarMappers.BOOLEAN_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final String queryString(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query string\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.STRING_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : "";
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<String> queryStringList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<String>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.STRING_MAPPER.page(this.pageable) : ScalarMappers.STRING_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final Object[] queryObjects(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query Object[]\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            return jdbc().query(sqlAndArgs.getFirst(), ScalarMappers.OBJECTS_MAPPER.single(), (Object[]) sqlAndArgs.getSecond());
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    protected final List<Object[]> queryObjectsList(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Query List<Object[]>\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var result = jdbc().query(sqlAndArgs.getFirst(), this.pageable != null ? ScalarMappers.OBJECTS_MAPPER.page(this.pageable) : ScalarMappers.OBJECTS_MAPPER.all(), (Object[]) sqlAndArgs.getSecond());
            return result != null ? result : emptyList();
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.query: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    /**
     * 执行插入或者更新操作。
     *
     * @param sqlAndArgsBuilder
     *         待执行的SQL和参数。
     * @return 更新操作影响的记录数。
     * @throws IllegalArgumentException
     *         如果参数 {@code sqlAndArgs} 是{@code null}。
     * @see #getLastRowId()
     * @see #getLastRowLongId()
     */
    protected final int update(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareSqlAndArgs(sqlAndArgsBuilder);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Update\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            if (!this.autoKey.isEmpty()) {
                return jdbc().update(new SimplePreparedStatementCreator(sqlAndArgs, this.autoKey), this.keyHolder);
            } else {
                final var args = (Object[]) sqlAndArgs.getSecond();
                return jdbc().update(sqlAndArgs.getFirst(), args);
            }
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.update: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetAutoKey();
        }
    }

    /**
     * 调用无返回值的存储过程。
     *
     * @param sqlAndArgsBuilder
     *         待执行的SQL和参数。
     * @throws IllegalArgumentException
     *         如果参数 {@code sqlAndArgs} 是{@code null}。
     */
    protected final void call(
            SqlAndArgsBuilder sqlAndArgsBuilder
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareProcedureAndArgs(sqlAndArgsBuilder, null);

        if (this.logger.isTraceEnabled()) {
            logger.trace("Call store procedure\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            jdbc().call(newCallableStatementCreator(sqlAndArgs), asList(sqlAndArgs.getSecond()));
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.call: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        }
    }

    /**
     * 调用有返回值的存储过程，返回结果被映射为一个实体。
     *
     * @param sqlAndArgsBuilder
     *         待执行的SQL和参数。
     * @return 映射的实体。
     * @throws IllegalArgumentException
     *         如果参数 {@code sqlAndArgs} 是{@code null}。
     */
    protected final <T> T callAndQuerySingle(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            JdbcMapper<T> mapper
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareProcedureAndArgs(sqlAndArgsBuilder, mapper.single());

        if (this.logger.isTraceEnabled()) {
            logger.trace("Call store procedure and return result set\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var resultMap = jdbc().call(newCallableStatementCreator(sqlAndArgs), asList(sqlAndArgs.getSecond()));
            @SuppressWarnings("unchecked") final var result = (T) resultMap.get(RETURN_RESULT_SET_ARG_NAME);
            return result;
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.call: " + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    /**
     * 调用有返回值的存储过程，返回结果被映射为一个列表。
     *
     * @param sqlAndArgsBuilder
     *         待执行的SQL和参数。
     * @return 映射的列表。
     * @throws IllegalArgumentException
     *         如果参数 {@code sqlAndArgs} 是{@code null}。
     */
    protected final <T> List<T> callAndQueryList(
            SqlAndArgsBuilder sqlAndArgsBuilder,
            JdbcMapper<T> mapper
    ) {
        rejectIfNull(sqlAndArgsBuilder, "sqlAndArgs");

        final var sqlAndArgs = prepareProcedureAndArgs(sqlAndArgsBuilder, this.pageable != null ? mapper.page(this.pageable) : mapper.all());

        if (this.logger.isTraceEnabled()) {
            logger.trace("Call store procedure and return result set\n" + dumpSqlAndArgs(sqlAndArgs));
        }

        try {
            final var resultMap = jdbc().call(newCallableStatementCreator(sqlAndArgs), asList(sqlAndArgs.getSecond()));
            @SuppressWarnings("unchecked") final var result = (List<T>) resultMap.get(RETURN_RESULT_SET_ARG_NAME);
            return result;
        } catch (DataAccessException ex) {
            throw new IllegalStateException("Cannot call jdbc.call" + getMostSpecificCauseMessage(ex) + "\n" + dumpSqlAndArgs(sqlAndArgs), getMostSpecificCause(ex));
        } finally {
            resetPageable();
        }
    }

    /**
     * 获取最后一次插入操作自动生成的ID。
     *
     * @return 最后一次插入操作自动生成的ID。如果不存在则返回 {@code null}。
     * @throws org.springframework.dao.DataRetrievalFailureException
     *         如果自动生成ID的列的名字不正确，或者自动生成的ID不能被转化为 {@link Number}类型。
     * @see #setAutoKey(String)
     */
    public final Number getLastRowId() {
        if (this.keyHolder != null) {
            final var key = this.keyHolder.getKey();

            this.keyHolder = null;

            return key;
        } else {
            throw new DataRetrievalFailureException("Cannot retrieve last row id while missed auto key");
        }
    }

    /**
     * 获取最后一次插入操作自动生成的ID。
     *
     * @return 最后一次插入操作自动生成的ID。如果不存在则返回 {@code -1}。
     * @throws org.springframework.dao.DataRetrievalFailureException
     *         如果自动生成ID的列的名字不正确，或者自动生成的ID不能被转化为 {@link Number}类型。
     * @see #setAutoKey(String)
     * @see #getLastRowId()
     */
    public final long getLastRowLongId() {
        final var id = getLastRowId();
        return id != null ? id.longValue() : -1;
    }

    /**
     * 标识符的大小写模式。
     */
    public enum IdentifierCaseMode {
        /**
         * 构造SQL语句时需要转为大写。
         */
        UPPER,
        /**
         * 构造SQL语句时需要转为小写。
         */
        LOWER,
        /**
         * 构造SQL语句时保持不变。
         */
        KEEP_UP
    }

    private static class SimplePreparedStatementCreator
            implements PreparedStatementCreator, SqlProvider {

        private final String sql;
        private final String autoKey;
        private final SqlParameter[] args;

        SimplePreparedStatementCreator(
                Pair<String, SqlParameter[]> sqlAndArgs,
                String autoKey
        ) {
            this(sqlAndArgs.getFirst(), autoKey, sqlAndArgs.getSecond());
        }

        SimplePreparedStatementCreator(
                String sql,
                String autoKey,
                SqlParameter[] args
        ) {
            assert sql != null;
            assert autoKey != null;
            assert args != null;

            this.sql = sql;
            this.autoKey = autoKey;
            this.args = args;
        }

        @Override
        public PreparedStatement createPreparedStatement(
                Connection con
        )
                throws SQLException {
            final PreparedStatement result;
            if (this.autoKey.isEmpty()) {
                result = con.prepareStatement(this.sql);
            } else if (this.autoKey.equals("*")) {
                result = con.prepareStatement(sql, RETURN_GENERATED_KEYS);
            } else {
                result = con.prepareStatement(sql, new String[]{this.autoKey});
            }

            new ArgumentPreparedStatementSetter(this.args) {
                @Override
                protected void doSetValue(
                        PreparedStatement ps,
                        int parameterPosition,
                        Object argValue
                )
                        throws SQLException {
                    assert argValue instanceof SqlParameterValue;

                    final var sv = (SqlParameterValue) argValue;
                    if (sv.getSqlType() == Types.BINARY) {
                        if (sv.getValue() instanceof ByteBuffer) {
                            super.doSetValue(ps, parameterPosition, new SqlParameterValue(sv.getSqlType(), sv.getName(), ((ByteBuffer) sv.getValue()).array()));
                            return;
                        }

                        // 对于Binary类型的参数，值只能是 null 或者 ByteBuffer 或者 byte[]
                        assert sv.getValue() == null || sv.getValue() instanceof byte[];
                    }

                    super.doSetValue(ps, parameterPosition, argValue);
                }
            }.setValues(result);

            return result;
        }

        @Override
        public String getSql() {
            return this.sql;
        }
    }

    private static class SimpleCallableStatementCreator
            implements CallableStatementCreator, SqlProvider {
        private final String sql;
        private final SqlParameter[] args;

        SimpleCallableStatementCreator(
                Pair<String, SqlParameter[]> sqlAndArgs
        ) {
            this(sqlAndArgs.getFirst(), sqlAndArgs.getSecond());
        }

        SimpleCallableStatementCreator(
                String sql,
                SqlParameter[] args
        ) {
            assert sql != null;
            assert args != null;

            this.sql = sql;
            this.args = args;
        }

        @Override
        public CallableStatement createCallableStatement(
                Connection con
        )
                throws SQLException {
            final CallableStatement result;
            final Object[] args;
            if (stream(this.args).anyMatch(SqlParameter::isResultsParameter)) {
                result = con.prepareCall(this.sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY);
                args = stream(this.args).filter(arg_ -> !arg_.isResultsParameter()).toArray(SqlParameter[]::new);
            } else {
                result = con.prepareCall(this.sql);
                args = this.args;
            }

            new ArgumentPreparedStatementSetter(args) {
                @Override
                protected void doSetValue(
                        PreparedStatement ps,
                        int parameterPosition,
                        Object argValue
                )
                        throws SQLException {
                    assert ps instanceof CallableStatement;
                    assert argValue instanceof SqlParameter;

                    if (argValue instanceof SqlParameterValue) {
                        final var sv = (SqlParameterValue) argValue;
                        if (sv.getSqlType() == Types.BINARY) {
                            if (sv.getValue() instanceof ByteBuffer) {
                                super.doSetValue(ps, parameterPosition, new SqlParameterValue(sv.getSqlType(), sv.getName(), ((ByteBuffer) sv.getValue()).array()));
                                return;
                            }

                            // 对于Binary类型的参数，值只能是 null 或者 ByteBuffer 或者 byte[]
                            assert sv.getValue() == null || sv.getValue() instanceof byte[];
                        }
                    }

                    final var cs = (CallableStatement) ps;
                    final var arg = (SqlParameter) argValue;
                    if (arg.isResultsParameter()) {
                        // 跳过返回值参数。
                    } else if (arg.isInputValueProvided()) {
                        super.doSetValue(ps, parameterPosition, argValue);
                    } else {
                        cs.registerOutParameter(parameterPosition, arg.getSqlType());
                    }
                }
            }.setValues(result);

            return result;
        }

        @Override
        public String getSql() {
            return this.sql;
        }
    }
}
